package com.codeboy.mediasoupandroiddemo.room;

import android.content.Context;
import android.util.Log;

import com.codeboy.mediasoupandroiddemo.media.MediaCapturer;
import com.codeboy.mediasoupandroiddemo.request.Request;
import com.codeboy.mediasoupandroiddemo.socket.EchoSocket;

import org.json.JSONException;
import org.json.JSONObject;
import org.mediasoup.droid.Consumer;
import org.mediasoup.droid.Device;
import org.mediasoup.droid.MediasoupException;
import org.mediasoup.droid.Producer;
import org.mediasoup.droid.RecvTransport;
import org.mediasoup.droid.SendTransport;
import org.mediasoup.droid.Transport;
import org.webrtc.EglBase;
import org.webrtc.MediaStreamTrack;
import org.webrtc.RTCUtils;
import org.webrtc.RtpParameters;
import org.webrtc.SurfaceViewRenderer;
import org.webrtc.VideoTrack;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;

/**
 * @Author: Codeboy 2685312867@qq.com
 * @Date: 2020-04-04 16:43
 */

public class RoomClient {
    private static final int STATS_INTERVAL_MS = 3000;
    private static final String TAG = "RoomClient";

    private final EchoSocket mSocket;
    private final MediaCapturer mMediaCapturer;
    private final ConcurrentHashMap<String, Producer> mProducers;
    private final ConcurrentHashMap<String, Consumer> mConsumers;
    private final List<JSONObject> mConsumersInfo;

    private final Device mDevice;
    private final RoomListener mListener;

    private Boolean mJoined;

    private SendTransport mSendTransport;
    private RecvTransport mRecvTransport;

    public RoomClient(EchoSocket socket, Device device, RoomListener listener) {
        mSocket = socket;
        mDevice = device;
        mProducers = new ConcurrentHashMap<>();
        mConsumers = new ConcurrentHashMap<>();
        mMediaCapturer = new MediaCapturer();
        mConsumersInfo = new ArrayList<>();
        mListener = listener;
        mJoined = false;
    }

    public void join() throws Exception {
        if (!mDevice.isLoaded()) {
            throw new IllegalStateException("Device is not loaded");
        }

        if (mJoined) {
            Log.w(TAG, "join() room already joined");
            return;
        }

        JSONObject device = new JSONObject();
        device.put("name", "android");
        device.put("flag", "10");
        device.put("version", "10");
        Request.sendLoginRoomRequest(mSocket, "codeboy", device,
                new JSONObject(mDevice.getRtpCapabilities()));

        mJoined = true;
        Log.d(TAG, "join() room joined");

    }

    public void createSendTransport()
            throws Exception {
        // Do nothing if send transport is already created
        if (mSendTransport != null) {
            Log.w(TAG, "createSendTransport() send transport is already created..");
            return;
        }

        createWebRtcTransport("send");
    }

    public void createRecvTransport()
            throws Exception {
        // Do nothing if recv transport is already created
        if (mRecvTransport != null) {
            Log.w(TAG, "createRecvTransport() recv transport is already created..");
            return;
        }

        createWebRtcTransport("recv");
    }

    public VideoTrack produceVideo(Context context,
                                   SurfaceViewRenderer localVideoView,
                                   EglBase.Context eglContext)
            throws Exception {
        if (mSendTransport == null) {
            throw new IllegalStateException("Send Transport not created");
        }

        if (!mDevice.canProduce("video")) {
            throw new IllegalStateException("Device cannot produce video");
        }

        mMediaCapturer.initCamera(context);
        VideoTrack videoTrack = mMediaCapturer.createVideoTrack(context,
                localVideoView, eglContext);

        String codecOptions = "[{\"videoGoogleStartBitrate\":1000}]";
        List<RtpParameters.Encoding> encodings = new ArrayList<>();
        encodings.add(RTCUtils.genRtpEncodingParameters(false, 500000, 0, 60, 0, 0.0d, 0L));
        encodings.add(RTCUtils.genRtpEncodingParameters(false, 1000000, 0, 60, 0, 0.0d, 0L));
        encodings.add(RTCUtils.genRtpEncodingParameters(false, 1500000, 0, 60, 0, 0.0d, 0L));
        createProducer(videoTrack, codecOptions, encodings);
        return videoTrack;
    }

    public void produceAudio()
            throws MediasoupException {
        if (mSendTransport == null) {
            throw new IllegalStateException("Send Transport not created");
        }

        if (!mDevice.canProduce("audio")) {
            throw new IllegalStateException("Device cannot produce audio");
        }

        String codecOptions = "[{\"opusStereo\":true},{\"opusDtx\":true}]";
        createProducer(mMediaCapturer.createAudioTrack(), codecOptions, null);
        Log.d(TAG, "produceAudio() audio produce initialized");
    }

    public void consumeTrack(JSONObject consumerInfo)
            throws JSONException, MediasoupException {
        if (mRecvTransport == null) {
            // User has not yet created a transport for receiving so temporarily store it
            // and play it when the recv transport is created
            //mConsumersInfo.add(consumerInfo);
            mConsumersInfo.add(consumerInfo);
            return;
        }

        final String kind = consumerInfo.getString("kind");

        final String id = consumerInfo.getString("id");
        final String producerId = consumerInfo.getString("producerId");
        final String rtpParameters = consumerInfo.getJSONObject("rtpParameters").toString();
        final Consumer.Listener listener = consumer -> Log.d(TAG, "consumer::onTransportClose");

        Consumer kindConsumer = mRecvTransport.consume(listener, id, producerId, kind, rtpParameters);
        mConsumers.put(kindConsumer.getId(), kindConsumer);
        Log.d(TAG, "consumerTrack() consuming id=" + kindConsumer.getId());
        mListener.onNewConsumer(kindConsumer);


    }



    private void createWebRtcTransport(String direction)
            throws Exception {
        JSONObject createWebRtcTransportResponse = Request.sendCreateWebRtcTransportRequest(mSocket, direction);
        Log.i("createWebRtcTransportResponse", createWebRtcTransportResponse.toString());
        JSONObject webRtcTransportData = createWebRtcTransportResponse;

        String id = webRtcTransportData.getString("id");
        String iceParametersString = webRtcTransportData.getJSONObject("iceParameters").toString();
        String iceCandidatesArrayString = webRtcTransportData.getJSONArray("iceCandidates").toString();
        String dtlsParametersString = webRtcTransportData.getJSONObject("dtlsParameters").toString();

        switch (direction) {
            case "send":
                createLocalWebRtcSendTransport(id, iceParametersString, iceCandidatesArrayString, dtlsParametersString);
                break;
            case "recv":
                createLocalWebRtcRecvTransport(id, iceParametersString, iceCandidatesArrayString, dtlsParametersString);
                break;
            default:
                throw new IllegalStateException("Invalid Direction");
        }

    }

    private void createLocalWebRtcSendTransport(String id, String remoteIceParameters, String remoteIceCandidatesArray, String remoteDtlsParameters)
            throws MediasoupException {
        final SendTransport.Listener listener = new SendTransport.Listener() {
            @Override
            public void onConnect(Transport transport, String dtlsParameters) {
                Log.d(TAG, "sendTransport::onConnect");
                handleLocalTransportConnectEvent(transport, dtlsParameters);
            }

            @Override
            public String onProduce(Transport transport, String kind, String rtpParameters, String appData) {
                Log.d(TAG, "sendTransport::onProduce kind=" + kind);
                return handleLocalTransportProduceEvent(transport, kind, rtpParameters, appData);
            }


            @Override
            public void onConnectionStateChange(Transport transport, String connectionState) {

            }
        };
        mSendTransport = mDevice.createSendTransport(listener, id, remoteIceParameters, remoteIceCandidatesArray, remoteDtlsParameters);
        Log.d(TAG, "Send Transport Created id=" + mSendTransport.getId());
    }

    private void createLocalWebRtcRecvTransport(String id,
                                                String remoteIceParameters,
                                                String remoteIceCandidatesArray,
                                                String remoteDtlsParameters) throws MediasoupException {
        final RecvTransport.Listener listener = new RecvTransport.Listener() {
            @Override
            public void onConnect(Transport transport, String dtlsParameters) {
                Log.d(TAG, "recvTransport::onConnect");
                handleLocalTransportConnectEvent(transport, dtlsParameters);
            }

            @Override
            public void onConnectionStateChange(Transport transport, String newState) {
                Log.d(TAG, "recvTransport::onConnectionStateChange newState=" + newState);
            }
        };
        mRecvTransport = mDevice.createRecvTransport(listener, id, remoteIceParameters, remoteIceCandidatesArray, remoteDtlsParameters);
        Log.d(TAG, "Recv Transport Created id=" + mRecvTransport.getId());


    }

    private void handleLocalTransportConnectEvent(Transport transport, String dtlsParameters) {
        try {
            Request.sendConnectWebRtcTransportRequest(mSocket, transport.getId(), dtlsParameters);
        } catch (JSONException e) {
            e.printStackTrace();
        }
    }

    private String handleLocalTransportProduceEvent(Transport transport,
                                                    String kind,
                                                    String rtpParameters,
                                                    String s2) {
        try {
            JSONObject transportProduceResponse = Request.sendProduceWebRtcTransportRequest(mSocket, transport.getId(), kind, rtpParameters);
            return transportProduceResponse.getString("id");
        } catch (Exception e) {
            Log.e(TAG, "transport::onProduce failed", e);
            return null;
        }

    }


    private void createProducer(MediaStreamTrack track,
                                String codecOptions,
                                List<RtpParameters.Encoding> encodings) throws MediasoupException {
        final Producer.Listener listener = producer -> Log.d(TAG, "producer::onTransportClose kind=" + track.kind());

        Producer kindProducer = mSendTransport.produce(listener, track, encodings, codecOptions);
        mProducers.put(kindProducer.getId(), kindProducer);

    }

    private Consumer getConsumerById(String id) {
        for (Consumer consumer : mConsumers.values()) {
            if (consumer.getId().equals(id)) {
                return consumer;
            }
        }

        throw new IllegalStateException("No " + id + " Consumer");
    }


    private Consumer getConsumerByKind(String kind) {
        for (Consumer consumer : mConsumers.values()) {
            if (consumer.getKind().equals(kind)) {
                return consumer;
            }
        }

        throw new IllegalStateException("No " + kind + " Consumer");
    }


    public void resumeRemoteConsumer(String id) throws JSONException {
        Request.sendResumeConsumerRequest(mSocket, id);
    }
}
